//
//  GQJMetaclassController.m
//  TotalDemo
//
//  Created by gujian on 16/7/20.
//  Copyright © 2016年 gujian. All rights reserved.
//

#import "GQJMetaclassController.h"
#import <objc/runtime.h>

@implementation GQJMetaclassController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
//    [self initSubviews];
    [self analyzingMeta];
    
    [self testVI];
    
    //测试runtime 方法
//    [self getIvarList];
//    [self getIvarLayout];
//    [self getMethod];
//    [self getMethodList];
//    [self replaceMethod];
//    [self alterRespondsToSelector];
    [self alterProtocol];
}

//void* id区别
- (void)testVI
{
    NSString *str = @"测试void*和id的区别";
    void * p = (__bridge void *)(str);
    id pID = str;
    NSLog(@"p:%@",(__bridge NSString *)p);
    NSLog(@"pID:%@",pID);
    
//    [p initSubviews];     //complier会提示错误 Bad reveiver type void*
//    [pID initSubviews];   //complier不会提示错误，但是会发生运行时错误
    
//    int a = 5;
//    void *pp = a;           //complier不会提示编译错误,但是有警告Incompatible integer
//                            //to pointer conversion initializing 'void *'with an expression of type 'int'
//    id ppID = a;            //complier会提示编译错误，Implicit conversion of 'int' to 'id' is disallowed with ARC
//                            //因为a不是OC对象
//    NSLog(@"pp:%d",(int)pp);
    
}

/**
 *  void * 不能直接指向OC对象，需要经过__bridge桥接转换, id可指向任意OC类型对象，无需额外操作
 *  void* 类型数据调用已存在的方法会提示编译错误，id类型对象不会提示编译错误（有可能引起运行错误）
 *  表明void* 类型数据不可接受消息
 *
 */

- (void)initSubviews
{
    UIImage *image = [UIImage imageNamed:@"metaclass"];
    
    CGSize targetSize = image.size;
    CGFloat ratio = targetSize.width / targetSize.height;
    
    targetSize.width = SCREEN_WIDTH;
    targetSize.height = targetSize.width / ratio;
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, targetSize.width, targetSize.height)];
    imageView.image = image;
    [self.view addSubview:imageView];
}

- (void)analyzingMeta
{
    [self createGQJError];
    
}

//使用runtime创建新类
- (void)createGQJError
{
    Class GQJError = objc_allocateClassPair([NSError class], "GQJError", 0);
    
#pragma clang diagonstic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    //add instance methods
    class_addMethod([GQJError class], @selector(report), (IMP)ReportFunction, "v@:");
    
    //类方法
    Class metaClass = object_getClass(GQJError);
    class_addMethod(metaClass, @selector(printDesc), (IMP)PrintDesc, "v@:");//GQJError
    
    //添加变量
    const char * errorNameType = @encode(CGRect);
    NSUInteger size;
    NSUInteger alignment;
    NSGetSizeAndAlignment(errorNameType, &size, &alignment);
    class_addIvar([GQJError class], "errorName", size, alignment, errorNameType);
    
    
    Class isClass = objc_lookUpClass("GQJError");
    if (!isClass) {
        objc_registerClassPair(GQJError);
    }else
        GQJError = isClass;
    
    id gqjError = [[GQJError alloc] initWithDomain:@"testDomain" code:0 userInfo:nil];
    
    //get the errorName value
    CGRect rect = CGRectMake(0, 0, 100, 100);
    [gqjError setValue:[NSValue valueWithCGRect:rect] forKey:@"errorName"];
    __unused CGRect resultRect = [[gqjError valueForKey:@"errorName"] CGRectValue];

    //invoke the Class method printDesc
    [GQJError performSelector:@selector(printDesc)];
    
    //invoke the instance method report
    [gqjError performSelector:@selector(report)];
    
    
#pragma clang diagnostic pop
    
}

void ReportFunction(id self, SEL _cmd)
{
    NSLog(@"This object is %p",self);
    NSLog(@"Class is %@, and super is %@", [self class], [self superclass]);
    
    Class currentClass = [self class];
    for (int i = 1; i < 5; i++) {
        const char * className = object_getClassName(currentClass);
        NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
        NSLog(@"class:%s is meta class:%d",className, class_isMetaClass(currentClass));
        currentClass = object_getClass(currentClass);
    }
    
    NSLog(@"NSObject's class is %p", [NSObject class]);
    NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
    
}

/**
 *  The Result listed
 *  This object is 0x7fb8fbc503c0                                                   该对象的地址
 *  Class is GQJError, and super is NSError
 *
 *  Following the isa pointer 1 times gives 0x7fb8fbc50a00   className:GQJError     该类的地址
 *  Following the isa pointer 2 times gives 0x7fb8fbc4c3c0   className:NSObject     该类的meta-class地址
 *  Following the isa pointer 3 times gives 0x1039dd198   className:NSObject        该类的meta-class的类地址
 *  Following the isa pointer 4 times gives 0x1039dd198   className:NSObject
 *
 *  NSObject's class is 0x1039dd170             NSObject类的地址
 *  NSObject's meta class is 0x1039dd198        NSObject的meta-class地址
 */

// class method
void PrintDesc(id self, SEL _cmd)
{
    NSLog(@"This is a class methods");
}

//已NSString 为例

/**获取实例变量和属性 Objective-C没有类变量这一概念，但是实际上是支持的，为全局变量
 例如 UIKIT_EXTERN
 
 */
- (void)getIvarList
{
    unsigned int ivarCount = 0;
    Ivar *ivarList = class_copyIvarList([NSString class], &ivarCount);
    for(int i = 0; i < ivarCount; i++)
    {
        Ivar temp = ivarList[i];
        const char *name = ivar_getName(temp);
        const char *type = ivar_getTypeEncoding(temp);
        NSLog(@"%s type is %s", name, type);
    }
    
    free(ivarList);
    ivarList = nil;
    
    unsigned int propertyCount = 0;
    objc_property_t *propertyList =  class_copyPropertyList([NSString class], &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t temp = propertyList[i];
        const char *name = property_getName(temp);
        const char *property = property_getAttributes(temp);
        NSLog(@"%s property is %s", name, property);
    }
    
    free(propertyList);
    propertyList = nil;
}

/** 获取strong修饰的实例变量 */
- (void)getIvarLayout
{
    const uint8_t *description = class_getIvarLayout(NSClassFromString(@"GQJContainerController"));
    NSLog(@"description:%p",description);
}

/** 获取某一Method */
- (void)getMethod
{
    Class buttonClass = [UIButton class];
    Method instanceMethod = class_getInstanceMethod(buttonClass, @selector(titleForState:));
    Method methodOfClass = class_getClassMethod(buttonClass, @selector(buttonWithType:));
    
    size_t len = 1024;
    char methodReturnType[len];
    method_getReturnType(instanceMethod, methodReturnType, len);
    NSLog(@"instanceMethod's returnType: %s",methodReturnType);
    NSLog(@"instanceMethod's returnType and paramter's type:%s",method_getTypeEncoding(methodOfClass));
    
    method_getReturnType(methodOfClass, methodReturnType, len);
    NSLog(@"classMethod's returnType: %s", methodReturnType);
    

}

/** 获取实例方法列表或类方法列表 */
- (void)getMethodList
{
    unsigned int methodCount = 0;
    Class buttonClass = [UIButton class];
    //获取实例方法
    Method *instanceMethodList = class_copyMethodList(buttonClass, &methodCount);
    for (int i = 0; i < methodCount; i++) {
        Method temp = instanceMethodList[i];
        SEL selName = method_getName(temp);
        const char *methodName = sel_getName(selName);
        NSLog(@"instance methodName:%s",methodName);
    }
    
    //获取类方法
    Method *methodList = class_copyMethodList(object_getClass(buttonClass), &methodCount);
    for (int i = 0; i < methodCount; i++) {
        Method temp = methodList[i];
        SEL selName = method_getName(temp);
        const char *methodName = sel_getName(selName);
        NSLog(@"class methodName:%s",methodName);
    }
}

- (void)replaceMethod
{
     [self originInfo];
    
    SEL originSelector = @selector(originInfo);
    SEL replaceSelector = @selector(replaceInfo);
    
    Class targetClass = [self class];
    Method replaceMethod = class_getInstanceMethod(targetClass, replaceSelector);
    
    //method_getImplementation(replaceMethod)
    //class_getMethodImplementation
    //class_getMethodImplementation_stret
    class_replaceMethod(targetClass, originSelector, class_getMethodImplementation_stret(targetClass, replaceSelector), method_getTypeEncoding(replaceMethod));
    
    [self originInfo];
    
}

- (void)originInfo
{
    //测试方法
    NSLog(@"原始方法");
}

- (void)replaceInfo
{
    //替代方法
    NSLog(@"替代方法");
}

#pragma mark - Medthod respondsToSelector

- (void)alterRespondsToSelector
{
    Class targetClass = [self class];
    SEL aSelector = @selector(originInfo);
    SEL bSelector = @selector(bbbInfo);
    
    BOOL canRespondsA = class_respondsToSelector(targetClass, aSelector);
    BOOL canRespondsB = class_respondsToSelector(targetClass, bSelector);
    
    NSLog(@"canRespondsA:%@,canRespondsB:%@",@(canRespondsA),@(canRespondsB));

}

- (void)alterProtocol
{
    //添加新protocol
   Protocol *GQJMetaProtocol = objc_allocateProtocol("GQJMetaProtocol");
    //添加方法
   SEL targetSelector = @selector(metaViewWillAppear);
   protocol_addMethodDescription(GQJMetaProtocol, targetSelector, "v@:", YES, YES);
    //添加属性
    //http://stackoverflow.com/questions/7819092/how-can-i-add-properties-to-an-object-at-runtime/7834787#7834787
    //https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
    objc_property_attribute_t type = {"T","@\"NSString\""};
    objc_property_attribute_t ownership1 = {"N",""};
    objc_property_attribute_t ownership2 = {"S",""};
    objc_property_attribute_t attrs[] = {type,ownership1,ownership2};
    protocol_addProperty(GQJMetaProtocol, "metaName", attrs, 2, YES, YES);
   objc_registerProtocol(GQJMetaProtocol);
    
    //遵从新协议
    Class targetClass = [self class];
    BOOL isSucceed = class_addProtocol(targetClass, GQJMetaProtocol);
    if (isSucceed) {
        BOOL isConformsToProtocol = class_conformsToProtocol(targetClass, GQJMetaProtocol);
        NSLog(@"isConforms to GQJMetaProtocol: %@",@(isConformsToProtocol));
    }
    
    unsigned int protocolCount = 0;
   __unsafe_unretained Protocol **protocolList = objc_copyProtocolList(&protocolCount);
    for (int i = 0; i < protocolCount; i++) {
        Protocol *tempProtocol = protocolList[i];
        const char *protocolName = protocol_getName(tempProtocol);
        NSLog(@"protocolName:%s",protocolName);
    }
    
    free(protocolList);
    
    Protocol *targetProtocol = objc_getProtocol("GQJMetaProtocol");
    NSLog(@"targetProtocol:%s",protocol_getName(targetProtocol));
    
    unsigned int propertyCount = 0;
    objc_property_t *propetyList = protocol_copyPropertyList(GQJMetaProtocol, &propertyCount);

    for (int i = 0; i < propertyCount; i++) {
        objc_property_t property = propetyList[i];
        const char *propertyName = property_getName(property);
        NSLog(@"propertyName:%s",propertyName);
    }
    
    struct objc_method_description signature = protocol_getMethodDescription(GQJMetaProtocol, targetSelector, YES, YES);
    NSLog(@"signature:%s,type:%s",sel_getName(signature.name), signature.types);
}


//#pragma mark - GQJMetaProtocol

- (void)metaViewWillAppear
{
    NSLog(@"%s",__func__);
}

@end








